#
# All views for blog app 
#
import calendar, re
from datetime import date, datetime, timedelta

from django import newforms as forms
from django.http import Http404
from django.shortcuts import get_object_or_404, render_to_response
from django.template import RequestContext
from django.views.generic.simple import redirect_to

from kangapy4.blog.common import CachedReadOnlyProperty, startOfMonth, \
        startOfNextMonth, startOfPrevMonth, \
        startOfNextYear, startOfPrevYear, \
        make_day_archive_url, make_month_archive_url, make_year_archive_url
from kangapy4.blog.models import PUBLISH_STATES, TRANSFORM_NAMES, \
        BlogEntry, Category, Comment, Document


# Date of earliest entry date
FIRST_DATE = None

# Maximum lengths in comment entry
MAX_EMAIL_LENGTH = 200
MAX_URL_LENGTH = 200

#
# Spam recognition
SPAM_EMAIL = re.compile("ebay\.com|search\.com|\.ru\Z|altavista.com", re.I)
SPAM_TEXT = re.compile("\Ahttp:|\Abuy|\A\w+ \w+\s*!+\Z", re.I)
SPAM_NAME = re.compile(
        r"buy|online|http|cheap|discount|generic|cardboard\.nu|http|" +
        r"chapter 7|manufacturer|\Ainfo|sperm|mp3", re.I)
SPAM_URL =re.compile(
        r"discount|insurance|money|\.ru/|\.info\b|\.pl|/guestbook|" +
        r"nigeria| \w+ |six\.de", re.I)
SPAM_ANYWHERE = re.compile(
        r"republika|mp3guy|poker\b|\bporn|ephedra|vicodin|cheap ?price|" +
        r"we just opened our cool|diazepam|dogbedstore|areaseo|" +
        r"zyban|xanax|prozac|levitra|cialis|viagra|ringtone|" +
        r"\bvalium\b|\bapidex\b|ultram|ambien|ativan|" +
        r"\.cz\b|zyworld|paris hilton|dating.*personals|" +
        r"pregnancy-|\[url=|phentermine|hydrocone|analgin|buy-info|" +
        r"tag-host|buy\.info|tramadol|dido mp3|u-blog|active\.com|" +
        r"casino|at-space\.com|lucky7|vuitton|rapidforum|" +
        r"dorank|page rank|zoloft|nexium|earn cash|href|funpic|" +
        r"mywebpage|pink diamonds|zetia|funpic|black-?jack|\.be\b|" +
        r"cgispy|\broulet|bondage|payday loans|online directory|" +
        r"yellow ?pages main|\.ro|" +
        r"\bkeno\b|paraesthesia|home owner insurance|" +
        r"\bbingo\b|tamiflu|bravenet|online gambling|klonopin|celebrex|" +
        r"pharmacy|embark\.to|comunalia|diethylpropion|" +
        r"butalibital|xin kai|script alert|proboards|1asphost|weightloss|" +
        r"opendiary|phpbbforfree|prodol\b|\Aorder |\Ainternet |xin kai|" +
        r"-facts\.info|adderall|vigrx|trazine\b|messageboard|kataweb|" +
        r"paintballgear|\btenuate\b|hydrocodone|floricet|" +
        r"alprazolam|disign|google pr|texas ?hold|spaces\.msn\.com|" +
        r"Content-Type:|\ALoved the site!|\AGood design|<script>|" +
        r"\Agood site|piranhoi|maxpages|imitrex|propecia|\Acool site" +
        r"zip\.dk|six\.de|download\.blogspot\.com", re.I)

SPAM_MSG = "This comment appears to be spam, and has not been accepted."


#### Utility functions ####

def by_category(queryset, cat):
    """ Filters by the given category.
        cat - The category by which to filter, or None if no filtering
              required
    """
    if not cat:
        return queryset

    return queryset.extra(
        tables=('blog_blogentry_categories bbec',),
        where=['bbec.blogentry_id = blog_blogentry.id', 
            'bbec.category_id = %s'],
        params=[cat.id])

def get_first_entry_date():
    """ Gets the date of the first entry, ever.
        Returns today's date if no entries found.
    """
    global FIRST_DATE
    if not FIRST_DATE:
        dates = BlogEntry.objects.published().order_by('created').values('created')
        if dates:
            FIRST_DATE = dates[0]['created'].date()
    return FIRST_DATE or date.today()

def make_link(url, label, cssClass):
    """ Make an object that can be used to render a link.
    """
    return dict(url=url, label=label, cssClass=cssClass)

def make_month_link(dt, category, cssClass):
    """ Make a link to a month/category archive
    """
    if startOfMonth(dt) >= startOfMonth(get_first_entry_date()) and \
            startOfMonth(dt) <= startOfMonth(date.today()):
        url = make_month_archive_url(dt, category)
        label = dt.strftime("%B, %Y")
        return make_link(url, label, cssClass)
    else:
        return None

def make_year_link(dt, category, cssClass):
    """ Make a link to a year/category archive
    """
    if dt.year >= get_first_entry_date().year and \
            dt.year <= date.today().year:
        url = make_year_archive_url(dt, category)
        label = dt.strftime("%Y")
        return make_link(url, label, cssClass)
    else:
        return None

def monthly_archive_dates():
    """ Gets a list of archive dates, by month.
    """
    d = get_first_entry_date()
    result = [d]
    d = startOfNextMonth(d)
    while d < date.today():
        result[0:0] = [d]
        d = startOfNextMonth(d)
    return result

def render(request, template_name, **kwargs):
    """ Helper method to render a template. 
    """
    return render_to_response(template_name, kwargs, RequestContext(request))

def render_blog_page(request, template_name, b_date, category=None, **kwargs):
    """ Renders the given page.
    """
    return render(request, template_name, 
            date=b_date,
            calendar=Calendar(b_date, category),
            categories=Category.objects.all(), 
            archive_dates=monthly_archive_dates(),
            category=category,
            **kwargs)

#### Calendar ####

class Calendar(object):
    """ Represents data in the calendar in the sidebox.
    """

    def __init__(self, month, category):
        """ Constructor.
        """
        self.month = startOfMonth(month)
        self.category = category

    @CachedReadOnlyProperty
    def entry_days(self):
        """ The set of day number for days that have an entry this month
        """
        query = BlogEntry.objects.published().filter(
                created__gte=self.month, 
                created__lt=startOfNextMonth(self.month))
        if self.category:
            # XXX Does this work?
            query = query.filter(categories=self.category)
            
        return set(d.day for d in query.dates('created', 'day'))

    def get_day_url(self, day):
        """ Get URL for the given day number in this month.
        """
        return make_day_archive_url(self.month + timedelta(day - 1), 
                self.category)

    def get_absolute_url(self):
        return make_month_archive_url(self.month, self.category)

    def has_entry_on_day(self, day):
        """ Is there an entry on the given day?
        """
        return day in self.entry_days

def get_category(catName):
    """ Gets a category named in a URL. 
    """
    if catName:
        return get_object_or_404(Category, name=catName)
    else: 
        return None


#### Comment Form ####

class CommentForm(forms.Form):
    """ Input for BlogEntry comments
    """
    author = forms.CharField(max_length=200, required=True)
    email = forms.EmailField(required=False)
    url = forms.URLField(required=False, label='')
    text = forms.CharField(max_length=5000, label='',
            widget=forms.Textarea(attrs=dict(rows=10, cols=50)))

    # implement individual field validation (for length of email and URL) here
    # (and non-ASCII chars)

    def clean(self):
        """ Cleans the data in this form.
        """
        d = self.clean_data
        is_spam = (SPAM_EMAIL.search(d.get('email') or '')
                or SPAM_NAME.search(d.get('author') or '')
                or SPAM_TEXT.search(d.get('text') or '')
                or SPAM_URL.search(d.get('url') or '')
                or SPAM_ANYWHERE.search(d.get('email') or '')
                or SPAM_ANYWHERE.search(d.get('author') or '')
                or SPAM_ANYWHERE.search(d.get('text') or ''))
        if is_spam:
            raise forms.ValidationError(SPAM_MSG)
        return d

    def clean_email(self):
        """ Ensure email field is not too long.
        """
        d = self.clean_data.get('email')
        if not d:
            return None
        if len(d) > MAX_EMAIL_LENGTH:
            raise forms.ValidationError('Email address too long')
        return d

    def clean_url(self):
        """ Ensure URL field is not too long.
        """
        d = self.clean_data.get('url')
        if not d:
            return None
        if len(d) > MAX_URL_LENGTH:
            raise forms.ValidationError('URL too long')
        return d

    def make_object(self, entry):
        """ Returns a new comment object, based on this form.
        """
        c = Comment()
        c.created = datetime.now()
        c.author = self.clean_data['author']
        c.email = self.clean_data['email']
        c.url = self.clean_data['url']
        c.text = self.clean_data['text']
        c.blog_entry = entry
        c.transform = TRANSFORM_NAMES.cooked2
        c.state = entry.calc_new_comment_state()
        return c

#### View functions ####

def blog_day(request, year, month, day, catName=None):
    """ Show blog entries for a given day
    """
    b_date = date(int(year), int(month), int(day))
    cat = get_category(catName)
    entries = BlogEntry.objects.published() \
            .filter(created__gte=b_date, created__lt=b_date + timedelta(1))
    entries = by_category(entries, cat)

    # Want to find next and previous days with entries in this category
    prev = make_day_link(startOfPrevDay(b_date), cat, 'nav_prev')
    next = make_day_link(startOfNextDay(b_date), cat, 'nav_next')

    return render_blog_page(request, 'blog/blog_day.html', 
            b_date, category=cat, entries=entries)

def blog_entry(request, year, month, day, slug):
    """ Show Single entry
    """
    clean_slug = slug.replace('_', '-')
    b_date = date(int(year), int(month), int(day))
    entries = BlogEntry.objects.published().filter(
            created__gte=b_date, 
            created__lt=b_date + timedelta(1),
            slug=clean_slug)

    # Ensure URL is valid
    if len(entries) == 0:
        raise Http404('Could not find a matching weblog entry.')
    if len(entries) > 1:
        raise Exception('There were %d entries for this URL.' % len(entries))
    entry = entries[0]
    if '_' in slug:
        # Migrate from '_' in slugs
        return redirect_to(request, url=entry.get_absolute_url())

    # Handle comment entry, if any
    saved = None
    preview = None
    if request.method == 'POST':
        form = CommentForm(request.POST)
        form.full_clean()
        if not form.errors:
            comment = form.make_object(entry)
            if request.POST.has_key('post'):
                saved = comment
                comment.save()
            else:
                preview = comment
    else:
        form = CommentForm()
    print form.errors

    return render_blog_page(request, 'blog/blog_entry.html', 
            b_date, entry=entry, 
            cform=form, saved_comment=saved, preview_comment=preview)

def blog_index(request, catName):
    """ Show most recent five weblog entries.
    """
    b_date = date.today()
    cat = get_category(catName)
    entries = BlogEntry.objects.published()
    entries = by_category(entries, cat)[:5]

    return render_blog_page(request, 'blog/blog_month.html', 
            b_date, category=cat, entries=entries)

def blog_month(request, year, month, catName=None):
    """ Show blog entries for a given month
    """
    b_date = date(int(year), int(month), 1)
    cat = get_category(catName)
    entries = BlogEntry.objects.published() \
            .filter(created__gte=b_date, created__lt=startOfNextMonth(b_date)) 
    entries = by_category(entries, cat)

    prev = make_month_link(startOfPrevMonth(b_date), cat, 'nav_prev')
    next = make_month_link(startOfNextMonth(b_date), cat, 'nav_next')

    return render_blog_page(request, 'blog/blog_month.html', 
            b_date, category=cat, entries=entries,
            prev_link=prev, next_link=next)

def blog_year(request, year, catName=None):
    """ Show blog entries for a year.
    """
    b_date = date(int(year), 1, 1)
    cat = get_category(catName)
    entries = BlogEntry.objects.published().filter(created__year = int(year)) \
            .order_by('created')
    entries = by_category(entries, cat)

    prev = make_year_link(startOfPrevYear(b_date), cat, 'nav_prev')
    next = make_year_link(startOfNextYear(b_date), cat, 'nav_next')

    return render_blog_page(request, 'blog/blog_year.html', 
            b_date, category=cat, entries=entries, 
            prev_link=prev, next_link=next)

def show_doc(request, slug):
    """ Shows a document from the database.
    """
    # Old slug URLs contain '_'. We want to migrate to '-'s
    if '_' in slug:
        alt_slug = slug.replace('_', '-')
        if Document.objects.filter(slug=alt_slug).count():
            return redirect_to(request, url='/docs/%s' % alt_slug)
        
    # Otherwise, just get the document and show it
    doc = get_object_or_404(Document, slug=slug)
    return render(request, 'blog/document.html', document=doc)
